package com.edu.udistrital.training;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;
import org.apache.lucene.index.TermFreqVector;
import org.apache.lucene.search.DefaultSimilarity;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.store.Directory;

import co.edu.udistrital.analyzer.LuceneIndexer;

public class IndexToArffConverter {
	
	/**
	 * Line separator character
	 */
	private final static String LINE_SEPARATOR = System.getProperty("line.separator");
	/**
	 * Constante para el texto del encabezado del documento arff
	 */
	private final static String ARFF_HEADER = "@relation LuceneToArff";
	/**
	 * Annotation for arff attribute
	 */
	private final static String ARFF_ATTRIBUTE = "@attribute";
	/**
	 * Data attribute for arff
	 */
	private final static String ARFF_DATA = "@data";
	/**
	 * Annotation for arff numeric type
	 */
	private final static String ARFF_NUMERIC_TYPE = "numeric";
	/**
	 * Escape literal
	 */
	private final static String ESCAPE = "\"";
	/**
	 * Classification category
	 */
	private final static String CLASSIFICATION_CATEGORY = "categoriaClasificacion";
	/**
	 * The arff type
	 */
	private final static String ARFF_TYPE = ".arff";
	/**
	 * Template file suffix
	 */
	private final static String TEMPLATE_FILE_SUFFIX = "Template";
	/**
	 * Classification file suffix
	 */
	private final static String CLASSIFICATION_FILE_SUFFIX = "Classification";
	/**
	 * Set owith all of terms extracted from the training documents
	 */
	private Set<String> terminosFinales;
	
	private TreeMap<String, Integer> terminos = new TreeMap<String, Integer>();
	
	private TreeMap<Term, Integer> categorias = new TreeMap<Term, Integer>();
	
	private int cantidadTotalTerminos;
	
	private int cantidadTotalCategorias;
	
	IndexReader index = null;

	
	public void convertToArff(String arffFileName, Directory directory) throws Exception {
        
        List<String> listaDocumentos = null;
        Set<String> setCategorias = null;
        List<String> listaAtributos = null;
        
        try {
	    	index = IndexReader.open(directory);
	    	
	        listaDocumentos = new ArrayList<String>();
	        setCategorias = new TreeSet<String>();
	        listaAtributos = new ArrayList<String>();
	        
	        TermEnum terms = index.terms();
	       
	        Term term = null; 
//	        int numeroTerminos = 0;
	        
//	        StringBuffer atributosTraducir = new StringBuffer();
	        
	        while(terms.next()) {
	        	term = terms.term();
	        	
	        	if(term != null && terminos.get(term.text()) == null
	        			&& LuceneIndexer.CONTENT.equals(term.field())) {
	        		//Term Frecuency
        			obtenerTermFrecuency(term);
	        	} 
	        	//Se cargan las categorias y el numero de archivos donde se encuentra la categoria
	        	else if(term != null && categorias.get(term) == null
	        			&& LuceneIndexer.CATEGORY.equals(term.field())) {
	        		obtenerCategoryFrecuency(term);
	        	}
	        }
	        
//	        TreeMap<String, Integer> terminosFinal = new TreeMap<String, Integer>();
	        terminosFinales = new TreeSet<String>();
	        int numeroTermino = 1;
	        //Information Gain
	        double entropiaSistema = obtenerEntropiaSistema();
	        
			for (Entry<String, Integer> termino : terminos.entrySet()) {
//				double informationGain = obtenerInformationGain(entropiaSistema, termino);
//				System.out.println(termino.getKey() + " - "+ termino.getValue() +" - "+informationGain);
				System.out.println(termino.getKey() + " - "+ termino.getValue());
//				if(informationGain > 0.1) {
//                	atributosTraducir.append(" "+ term.text());
//                	atributosTraducir.append(" ,");
                	
//                    String texto = ARFF_ATTRIBUTE + " " + ESCAPE + termino
//				+ ESCAPE + " " + ARFF_NUMERIC_TYPE + LINE_SEPARATOR;
                    String texto = ARFF_ATTRIBUTE + " " + ESCAPE + termino.getKey()
        					+ ESCAPE + " " + ARFF_NUMERIC_TYPE + LINE_SEPARATOR;
                    listaAtributos.add(texto);
                    
                    terminosFinales.add(termino.getKey());
                    numeroTermino ++;
//				}

			}
			
	        for(int i=0; i< index.numDocs(); i++) {
	        	
	            TermFreqVector termFreqVector = index.getTermFreqVector(i, LuceneIndexer.CONTENT);
	            String texto = " ";
	            
	            if (termFreqVector != null) {
	                String[] terminosUsados = termFreqVector.getTerms();
	                int[] freq = termFreqVector.getTermFrequencies();
	                
	    			Similarity similary = new DefaultSimilarity();
	    			float tf = 0;			
	    			float mev = 0;
	    			float idf = 0;
	                
	                Iterator<String> nombresTerminos= terminosFinales.iterator();
	                while (nombresTerminos.hasNext()) {
	                	String nombreTermino = nombresTerminos.next();
	                	int cantidadDocumentosTermino = 0;
	                    int frecuenciaTermino = 0;
	                    for(int k=0; (k< terminosUsados.length && frecuenciaTermino == 0); k++){
	                        String ter = terminosUsados[k];
	                        
	                        if(nombreTermino.equals(ter)) {
	                        	frecuenciaTermino = freq[k];
	                        	tf = similary.tf(frecuenciaTermino);
	                        	
	                        	braekTermino : for (Entry<String, Integer> termino : terminos.entrySet()) {
	                        		if(termino.equals(ter)){
	                        			TermDocs termDocs = index.termDocs(term);
	                        	        while(termDocs.next()) {
	                        	        	cantidadDocumentosTermino +=1;
	                        	        }
	                        			
	                        			break braekTermino;
	                        		}
	                        	}
	                        	
	                        	//param : cantidad documentos aparece el termino , cantidad total de documentos
	                        	idf = similary.idf(cantidadDocumentosTermino, index.numDocs());
	    	                    mev = tf * idf;
	                        }
	                    }
	                    
	                    
	                    texto += " "+mev+", ";
					}
	            }
	            
	            Document document = index.document(i);
	            
	            texto += "'" + document.getFieldable(LuceneIndexer.CATEGORY).stringValue() + "'\n";
	            //TODO se cambia el lugar donde se carga el set
	            setCategorias.add(document.getFieldable(LuceneIndexer.CATEGORY).stringValue());
	            listaDocumentos.add(texto);
	        }
	        
        } finally {
        	index.close();
        }
      
        generateTemplateFile(arffFileName, listaAtributos, setCategorias);
        generateFile(arffFileName, listaDocumentos);
	}
	
	public void obtenerTermFrecuency(Term term) throws Exception {
		
		TermDocs termDocs = index.termDocs(term);
		int acumuladoFrecuencia = 0;
		
        while(termDocs.next()) {
            int frecuencia = termDocs.freq();
            acumuladoFrecuencia += frecuencia;
        }
        termDocs.close();
        
		if (acumuladoFrecuencia > 1000) {
			cantidadTotalTerminos += acumuladoFrecuencia;
			terminos.put(term.text(), acumuladoFrecuencia);
//			FIXME: ALEJO
//			String texto = ARFF_ATTRIBUTE + " " + ESCAPE + t.text()
//					+ ESCAPE + " " + ARFF_NUMERIC_TYPE + LINE_SEPARATOR;
//			attributes.add(texto);
		} 
	
	}
	
	public void obtenerCategoryFrecuency(Term term) throws Exception {
		
		TermDocs termDocs = index.termDocs(term);
		int acumuladoFrecuencia = 0;
		
        while(termDocs.next()) {
            int frecuencia = termDocs.freq();
            acumuladoFrecuencia += frecuencia;
        }	                
        termDocs.close();
        cantidadTotalCategorias += acumuladoFrecuencia;
        categorias.put(term, acumuladoFrecuencia);
	
	}
	
	public double obtenerInformationGain(double entropiaSistema, Entry<Term, Integer> termino) throws IOException {
		double informationGain = 0;
		
		double entropiaTermino = 0;
		double entropiaNoTermino = 0;
		
		//recorrer todos los docuementos donde se encuentre la categoria
		for (Entry<Term, Integer> categoria : categorias.entrySet()) {
			TermDocs termDocs = index.termDocs(categoria.getKey());
			//frecuencia de que 'termino' este en la Categoria 'categoria' P(Ci n t)
			int freqTermCategoria = 0;
			//frecuencia de que otros terminos que no sean 'termino' este en la Categoria 'categoria' P(Ci n !t)
			int freqNoTermCategoria = 0;
			
			while(termDocs.next()) {	
				TermFreqVector termFreqVector = index.getTermFreqVector(termDocs.doc(),LuceneIndexer.CONTENT); 
				if (termFreqVector != null) {
					String indexTerms[] = termFreqVector.getTerms(); 
					int indexFreqs[] = termFreqVector.getTermFrequencies(); 
	
					for(int i = 0; i<indexTerms.length; i++) { 
	                    String ter = indexTerms[i];
                        
                        if(termino.getKey().text().equals(ter)) {
                        	freqTermCategoria += indexFreqs[i];
                        }else {
                        	freqNoTermCategoria += indexFreqs[i];
                        }
					} 
				}
			}
			
			//P(Ci|t)
			double probablidadCondicionalTermino = Double.valueOf(freqTermCategoria) / Double.valueOf(termino.getValue());
			
			//P(Ci|t)*log2(P(Ci|t))
			if(probablidadCondicionalTermino > 0) {
				entropiaTermino += probablidadCondicionalTermino * (Math.log(probablidadCondicionalTermino) / Math.log(2));
			}
			
			//P(Ci|!t)
			double probabilidadCondicionalNoTermino = Double.valueOf(freqNoTermCategoria) / (cantidadTotalTerminos - Double.valueOf(termino.getValue()));
		
			//P(Ci|!t)*log2(P(Ci|!t))
			entropiaNoTermino += probabilidadCondicionalNoTermino * (Math.log(probabilidadCondicionalNoTermino) / Math.log(2));
		}

		//G(t) = - Sum(P(Ci)log2P(Ci)) + P(t)*Sum(P(Ci|t)*log2(P(Ci|t))) + P(!t)*Sum(P(Ci|!t)*log2(P(Ci|!t))) 
		informationGain = -entropiaSistema + ((Double.valueOf(termino.getValue()) / Double.valueOf(cantidadTotalTerminos)) * entropiaTermino) + ((Double.valueOf(cantidadTotalTerminos - Double.valueOf(termino.getValue())) / Double.valueOf(cantidadTotalTerminos)) * entropiaNoTermino); 
		
		return informationGain;
	}

	public double obtenerEntropiaSistema() {
		double entropiaSistema = 0;
		
		for (Entry<Term, Integer> categoria : categorias.entrySet()) {
			double probabilidadCategoria = Double.valueOf(categoria.getValue()) / Double.valueOf(cantidadTotalCategorias);
			//P(Ci)*log2P(Ci)
			entropiaSistema += probabilidadCategoria * (Math.log(probabilidadCategoria) / Math.log(2)); 
		}
		
		return entropiaSistema;
	}
    
    private void generateTemplateFile (String arffFileName, List<String> attributes,
    		Set<String> categories) throws IOException {
        FileOutputStream os = new FileOutputStream(arffFileName + TEMPLATE_FILE_SUFFIX + ARFF_TYPE);
        try {
	        //Escribe el encabezado arff en el archivo
	        String texto = ARFF_HEADER + LINE_SEPARATOR + LINE_SEPARATOR;
	        byte[] contenido = texto.getBytes();
	        os.write(contenido);
	        System.out.println(texto);
	        for (String atributo: attributes){
	        	texto = atributo;
	            contenido = texto.getBytes();
	            os.write(contenido);
	            System.out.println(texto);
	        }
	        StringBuffer categorias = new StringBuffer("");
	        for (String categoriaActual: categories){
	        	categorias.append("'" + categoriaActual + "'" + ",");
	        }
	        texto = ARFF_ATTRIBUTE + " " + CLASSIFICATION_CATEGORY + " " + "{" + categorias.toString() + "}";
	        contenido = texto.getBytes();
	        os.write(contenido);
	        System.out.println(texto);
	        texto = LINE_SEPARATOR + LINE_SEPARATOR + ARFF_DATA + LINE_SEPARATOR + LINE_SEPARATOR;
	        contenido = texto.getBytes();
	        os.write(contenido);
	        System.out.println(texto);
    	} catch (IOException e) {
			throw e;
    	} catch (Exception e) {
   			e.printStackTrace();
    	} finally {
            os.close();
    	}
    }
    
    private void generateFile (String arffFileName, List<String> documents) throws IOException {
        FileOutputStream os = new FileOutputStream(arffFileName +  ARFF_TYPE);
        FileInputStream is = new FileInputStream(arffFileName + TEMPLATE_FILE_SUFFIX +  ARFF_TYPE);
        try {
	        IOUtils.copy(is, os);
	        IOUtils.closeQuietly(is);
	        for (String documento: documents) {
	            String texto = documento;
	            byte[] contenido = texto.getBytes();
	            os.write(contenido);
	            //System.out.println(texto);
	        }
    	} catch (IOException e) {
			throw e;
    	} catch (Exception e) {
   			e.printStackTrace();
    	} finally {
            is.close();
            os.close();
    	}
    }
	
}
